It's common to use single case discriminated unions to meaningfully represent data values.
But EF does not know anything about this kind of type. Luckily this repository has some ways to help you to deal with they.
We have two approaches to deal with single case union types which are a converter or an extension that searches for all Single Case Unions in your entities.
open EntityFrameworkCore.FSharp.Extensions
type PositiveInteger = PositiveInteger of int
[<CLIMutable>]
type Blog = {
[<Key>]
Id : Guid
Title : string
Votes: PositiveInteger
}
type MyContext () =
inherit DbContext()
[<DefaultValue>]
val mutable private _blogs : DbSet<Blog>
member this.Blogs with get() = this._blogs and set v = this._blogs <- v
override _.OnModelCreating builder =
// setting manually each property
builder.Entity<Blog>()
.Property(fun x -> x.Votes)
.HasConversion(SingleCaseUnionConverter<int, PositiveInteger>())
|> ignore
// OR
// enables single clase unions for all entities
builder.RegisterSingleUnionCases()
override _.OnConfiguring(options: DbContextOptionsBuilder) : unit =
options
.UseSqlite( "Data Source=dbName.db")
.UseFSharpTypes() // enable queries for F# types
|> ignore
You can query for equality without any problem
let blog =
query {
for blog in ctx.Blogs do
where (blog.Votes = PositiveInteger 10)
select blog
headOrDefault
}
// or
let blog' = ctx.Blogs.Where(fun b -> b.Votes = PositiveInteger 10).FirstOrDefault()
For querying with other types of operation you will need to unwrap the value inside the query
let blogQuery =
query {
for blog in ctx.Blogs do
let (PositiveInteger votes) = blog.Votes
where (votes > 0)
select blog
headOrDefault
}
This extension doesn't support private union case constructors.
Multiple items
union case PositiveInteger.PositiveInteger: int -> PositiveInteger
--------------------
type PositiveInteger = | PositiveInteger of int
Multiple items
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> =
int
Multiple items
type CLIMutableAttribute =
inherit Attribute
new: unit -> CLIMutableAttribute
--------------------
new: unit -> CLIMutableAttribute
type Blog =
{
Id: Guid
Title: string
Votes: PositiveInteger
}
Multiple items
type KeyAttribute =
inherit Attribute
new: unit -> unit
<summary>Denotes one or more properties that uniquely identify an entity.</summary>
--------------------
KeyAttribute() : KeyAttribute
Blog.Id: Guid
Multiple items
[<Struct>]
type Guid =
new: b: byte array -> unit + 6 overloads
member CompareTo: value: Guid -> int + 1 overload
member Equals: g: Guid -> bool + 1 overload
member GetHashCode: unit -> int
member ToByteArray: unit -> byte array + 1 overload
member ToString: unit -> string + 2 overloads
member TryFormat: utf8Destination: Span<byte> * bytesWritten: byref<int> * ?format: ReadOnlySpan<char> -> bool + 1 overload
member TryWriteBytes: destination: Span<byte> -> bool + 1 overload
static member (<) : left: Guid * right: Guid -> bool
static member (<=) : left: Guid * right: Guid -> bool
...
<summary>Represents a globally unique identifier (GUID).</summary>
--------------------
Guid ()
Guid(b: byte array) : Guid
Guid(b: ReadOnlySpan<byte>) : Guid
Guid(g: string) : Guid
Guid(b: ReadOnlySpan<byte>, bigEndian: bool) : Guid
Guid(a: int, b: int16, c: int16, d: byte array) : Guid
Guid(a: int, b: int16, c: int16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : Guid
Guid(a: uint32, b: uint16, c: uint16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : Guid
Blog.Title: string
Multiple items
val string: value: 'T -> string
--------------------
type string = String
Blog.Votes: PositiveInteger
Multiple items
type MyContext =
new: unit -> MyContext
override OnConfiguring: options: 'a -> unit
override OnModelCreating: builder: 'a -> 'b
member Blogs: IQueryable<Blog>
--------------------
new: unit -> MyContext
Multiple items
type DefaultValueAttribute =
inherit Attribute
new: unit -> DefaultValueAttribute + 1 overload
member Check: bool
--------------------
new: unit -> DefaultValueAttribute
new: check: bool -> DefaultValueAttribute
MyContext._blogs: obj
val set: elements: seq<'T> -> Set<'T> (requires comparison)
val ignore: value: 'T -> unit
type unit = Unit
val blog: Blog
val query: Linq.QueryBuilder
val ctx: MyContext
property MyContext.Blogs: IQueryable<Blog> with get, set
custom operation: where (bool)
Calls Linq.QueryBuilder.Where
custom operation: select ('Result)
Calls Linq.QueryBuilder.Select
custom operation: headOrDefault
Calls Linq.QueryBuilder.HeadOrDefault
val blog': Blog
(extension) Collections.Generic.IEnumerable.Where<'TSource>(predicate: Func<'TSource,bool>) : Collections.Generic.IEnumerable<'TSource>
(extension) Collections.Generic.IEnumerable.Where<'TSource>(predicate: Func<'TSource,int,bool>) : Collections.Generic.IEnumerable<'TSource>
(extension) IQueryable.Where<'TSource>(predicate: Expressions.Expression<Func<'TSource,bool>>) : IQueryable<'TSource>
(extension) IQueryable.Where<'TSource>(predicate: Expressions.Expression<Func<'TSource,int,bool>>) : IQueryable<'TSource>
val b: Blog
val blogQuery: Blog
val votes: int